# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import datetime
import logging
import os
import sqlite3

from coin.base.datetime_util import to_datetime, to_timestamp_int


def _extract_timestamp_1(line):
  timestamp_format = '%Y-%m-%d %H:%M:%S,%f'  # Fucking european style
  splitted = line.split()
  try:
    dt = datetime.datetime.strptime('%s %s' % (splitted[1], splitted[2]), timestamp_format)
    return to_timestamp_int(dt)
  except (ValueError, IndexError):
    return None
  except MemoryError:
    logging.exception('Memory error on line %s', line)
    return None


def _extract_timestamp_2(line):
  timestamp_format = 'I%m%d %H:%M:%S.%f'
  splitted = line.split()
  try:
    now = datetime.datetime.now()
    dt = (datetime.datetime.strptime('%s %s' % (splitted[0], splitted[1]),
                                     timestamp_format).replace(year=now.year))
    if dt > now:
      dt = dt.replace(year=now.year - 1)
    return to_timestamp_int(dt)
  except (ValueError, IndexError):
    return None
  except MemoryError:
    logging.exception('Memory error on line %s', line)
    return None


def _get_first_timestamp(f_obj):
  timestamp_format = '%Y-%m-%d %H:%M:%S,%f'  # Fucking european style
  while True:
    line = f_obj.readline()
    if not line:
      break

    timestamp = _extract_timestamp_1(line)
    if timestamp is not None:
      return timestamp

    timestamp = _extract_timestamp_2(line)
    if timestamp is not None:
      return timestamp
  raise EOFError()


def _find_nearest_newline_pos(f_obj):
  while True:
    c = f_obj.read(1)
    if c is None:
      raise EOFError()
    elif c == '\n':
      return f_obj.tell() - 1


def _find_log_pos_by_timestamp(filepath, timestamp, hint_pos=0):
  timestamp = to_timestamp_int(timestamp)
  first = hint_pos
  end = os.path.getsize(filepath)
  ts = 0

  with open(filepath, 'r', errors='ignore') as f:
    count = end - first
    while count > 0:
      step = int(count / 2)
      mid = first + step
      f.seek(mid)
      try:
        ts = _get_first_timestamp(f)
      except EOFError:
        count = step
        continue

      if ts < timestamp:
        first = mid + 1
        count -= (step + 1)
      else:
        count = step

    if first == 0:
      return 0, ts
    f.seek(first)
    return _find_nearest_newline_pos(f) + 1, ts


def _get_cached_pos(filepath, db_filepath=None):
  db_filepath = db_filepath or '/tmp/monitor_cache.db'
  conn = sqlite3.connect(db_filepath)

  cached_ts = 0
  cached_pos = 0

  try:
    c = conn.cursor()
    c.execute('CREATE TABLE IF NOT EXISTS pos_cache '
              '(filepath TEXT PRIMARY KEY, timestamp INTEGER, pos INTEGER)')
    conn.commit()

    c.execute('SELECT * FROM pos_cache WHERE filepath=?', (filepath,))
    value = c.fetchone()
    if cached is not None:
      assert value[0] == filepath, '%s != %s' % (value[0], filepath)
      cached_ts = value[1]
      cached_pos = value[2]
  except Exception:
    pass
  finally:
    conn.close()

  return (cached_ts, cached_pos)


def _set_cached_pos(filepath, timestamp, pos, db_filepath=None):
  timestamp = to_timestamp_int(timestamp)
  db_filepath = db_filepath or '/tmp/monitor_cache.db'
  conn = sqlite3.connect(db_filepath)

  try:
    c = conn.cursor()
    c.execute('CREATE TABLE IF NOT EXISTS pos_cache '
              '(filepath TEXT PRIMARY KEY, timestamp INTEGER, pos INTEGER)')
    conn.commit()

    c.execute('INSERT OR REPLACE INTO pos_cache VALUES (?, ?, ?)', (filepath, timestamp, pos))
    conn.commit()
  except Exception:
    pass
  finally:
    conn.close()


def find_log_pos_by_timestamp(filepath, timestamp, use_cache=True):
  timestamp = to_timestamp_int(timestamp)
  hint_pos = 0

  if use_cache:
    cached_ts, cached_pos = _get_cached_pos(filepath)
    if cached_ts <= timestamp:
      hint_pos = cached_pos

  pos, ts = _find_log_pos_by_timestamp(filepath, timestamp, hint_pos)

  if use_cache:
    if cached_ts < ts:
      _set_cached_pos(filepath, ts, pos)

  return pos
